package com.controller.sys;

import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.common.base.BaseConstant;
import com.common.util.*;
import com.common.vo.ExceptionVo;
import com.entity.sys.*;
import com.service.sys.*;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;


@RestController
@RequestMapping("/sys")
public class SysLoginController {

    @Resource
    private SysSafeService sysSafeService;
    @Resource
    private SysUserService sysUserService;
    @Resource
    private SysLogService sysLogService;
    @Resource
    private RedisUtil redisUtil;

    /** 密码最大错误次数 */
    private int ERROR_COUNT = 3;

    /** 登录 */
    @PostMapping("/login")
    public ResultUtil login(String userName, String password, String code,HttpServletRequest request){
        //保存登录日志
        SysLog sysLog = new SysLog(IpUtil.getIpAddress(request),"用户登录","login");
        sysLog.setId(IdUtil.getSnowflakeNextIdStr());
        sysLog.setCreateBy(userName);
        sysLog.setState("登录成功");
        try {
            /*code = code.toUpperCase();
            Object verCode = redisUtil.get(BaseConstant.verCode+code);
            if (Objects.isNull(verCode)) {
                return ResultUtil.error("验证码已失效，请重新输入");
            }
            redisUtil.del(BaseConstant.verCode+code);
            password = RSAUtil.decrypt(password);   //密码私钥解密*/
            SysSafe safe = sysSafeService.list().get(0);
            SysUser user = passwordErrorNum(userName, password,safe);
            sysUserService.setDataScope(user); // 设置用户的数据范围查询条件
            String oldToken = StpUtil.getTokenValueByLoginId(user.getId()); // 先根据登录id获取token
            if (StrUtil.isNotBlank(oldToken)){
                StpUtil.logout(user.getId()); // 如果token不为空，先退出登录
            }
            StpUtil.login(user.getId());
            String tokenValue = StpUtil.getTokenValue();
            sysLog.setInfo(userName+"登录成功");
            StpUtil.getSession().set("user",user);
            sysLogService.save(sysLog);
            int i = safe.getIdleTimeSetting();
            //如果系统闲置时间为0，设置token和session永不过期
            if (i==0){
                SaTokenDao saTokenDao = SaManager.getSaTokenDao();
                SaTokenConfig config = SaManager.getConfig();
                saTokenDao.updateSessionTimeout(StpUtil.getSession().getId(),-1);
                saTokenDao.updateTimeout(BaseConstant.tokenCachePrefix+tokenValue,-1);
                saTokenDao.updateTimeout(BaseConstant.cachePrefix+"last-activity:"+tokenValue,-1);
                config.setActivityTimeout(-1);
            }
            return ResultUtil.success(tokenValue);
        } catch (ExceptionVo e) {
            sysLog.setInfo(e.getMessage());
            sysLog.setState("登录失败");
            sysLogService.save(sysLog);
            return ResultUtil.error(e.getCode(),e.getMessage());
        }catch (Exception e) {
            sysLog.setInfo(BaseConstant.UNKNOWN_EXCEPTION);
            sysLog.setState("登录失败");
            sysLogService.save(sysLog);
            e.printStackTrace();
            return ResultUtil.error(BaseConstant.UNKNOWN_EXCEPTION);
        }
    }

    /** 获取验证码 */
    @GetMapping(value = "/getCode")
    public void getCode(HttpServletResponse response) {
        try {
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
            response.setContentType("image/jpeg");

            RandomGenerator randomGenerator = new RandomGenerator(BaseConstant.captcha, 4);
            LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(100, 42,4,50);
            lineCaptcha.setGenerator(randomGenerator);
            BufferedImage image = lineCaptcha.getImage();
            OutputStream out = response.getOutputStream();
            redisUtil.set(BaseConstant.verCode+lineCaptcha.getCode(), lineCaptcha.getCode(), 60);
            ImageIO.write(image, "png", out);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /** 退出登录 */
    @DeleteMapping("/logout")
    public ResultUtil logout(HttpServletRequest request){
        String token = request.getHeader(BaseConstant.tokenHeader);
        //根据token退出登录
        StpUtil.logoutByTokenValue(token);
        return ResultUtil.success("退出登录成功");
    }

    /**
     * 产生public key
     */
    @GetMapping("/getKey")
    public ResultUtil getKey() {
        String publicKey = RSAUtil.getPublicKey();
        System.out.println(publicKey);
        return ResultUtil.success(publicKey);
    }

    /**
     * 判断账号是否锁定
     */
    private boolean lockedUser(long currentTime,String userName){
        boolean flag = false;
        if (redisUtil.hasKey(BaseConstant.ERROR_COUNT+userName)){
            long loginTime = Long.parseLong(redisUtil.hget(BaseConstant.ERROR_COUNT+userName, "loginTime").toString());
            int i = Integer.parseInt(redisUtil.hget(BaseConstant.ERROR_COUNT+userName,"errorNum").toString());
            if (i >= ERROR_COUNT && currentTime < loginTime){
                Duration between = LocalDateTimeUtil.between(LocalDateTimeUtil.of(currentTime), LocalDateTimeUtil.of(loginTime));
                throw new ExceptionVo(1004,"账号锁定中，还没到允许登录的时间，请"+between.toMinutes()+"分钟后再尝试");
            }else{
                flag = true;
            }
        }
        return flag;
    }

    /**
     * 密码错误次数验证
     */
    private SysUser passwordErrorNum(String userName,String password,SysSafe sysSafe) throws InvalidKeySpecException, NoSuchAlgorithmException {
        //查询用户
        SysUser user = sysUserService.getUser(null,userName);
        if (null == user){
            throw new ExceptionVo(1001,"用户不存在");
        }
        //根据前端输入的密码（明文），和加密的密码、盐值进行比较，判断输入的密码是否正确
        boolean authenticate = EncryptionUtil.authenticate(password, user.getPassword(), user.getSalt());
        if (authenticate) {
            //密码正确错误次数清零
            redisUtil.del(BaseConstant.ERROR_COUNT+userName);
        } else {
            long currentTime = System.currentTimeMillis();
            //判断账号是否锁定
            boolean flag = lockedUser(currentTime, userName);

            //错误3次，锁定15分钟后才可登陆 允许时间加上定义的登陆时间（毫秒）
            String str = "15";
            long timeStamp = System.currentTimeMillis()+900000;
            //密码登录限制（0：连续错3次，锁定账号15分钟。1：连续错5次，锁定账号30分钟）
            if (sysSafe.getPwdLoginLimit()==1){
                ERROR_COUNT = 5;
                str = "30";
                timeStamp = System.currentTimeMillis()+1800000;
            }
            //密码登录限制（0：连续错3次，锁定账号15分钟。1：连续错5次，锁定账号30分钟）
            if (redisUtil.hasKey(BaseConstant.ERROR_COUNT+userName)){
                int i = Integer.parseInt(redisUtil.hget(BaseConstant.ERROR_COUNT+userName,"errorNum").toString());
                if (flag && i==ERROR_COUNT){
                    redisUtil.hset(BaseConstant.ERROR_COUNT+userName,"errorNum",1);
                }else {
                    redisUtil.hincr(BaseConstant.ERROR_COUNT+userName,"errorNum",1);
                }
                redisUtil.hset(BaseConstant.ERROR_COUNT+userName,"loginTime",timeStamp);
            }else {
                Map<String,Object> map = new HashMap<>();
                map.put("errorNum",1);
                map.put("loginTime",timeStamp);
                redisUtil.hmset(BaseConstant.ERROR_COUNT+userName, map, -1);
            }
            int i = Integer.parseInt(redisUtil.hget(BaseConstant.ERROR_COUNT+userName,"errorNum").toString());
            if (i==ERROR_COUNT){
                throw new ExceptionVo(1004,"您的密码已错误"+ERROR_COUNT+"次，现已被锁定，请"+str+"分钟后再尝试");
            }
            throw new ExceptionVo(1000,"密码错误，总登录次数"+ERROR_COUNT+"次，剩余次数: " + (ERROR_COUNT-i));
        }
        return user;
    }

}
